package pers.qianyu.module.recommend.service.impl;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.log.LogFactory;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import org.apache.mahout.cf.taste.common.TasteException;
import org.apache.mahout.cf.taste.impl.common.FastByIDMap;
import org.apache.mahout.cf.taste.impl.common.LongPrimitiveIterator;
import org.apache.mahout.cf.taste.impl.model.GenericDataModel;
import org.apache.mahout.cf.taste.impl.model.GenericUserPreferenceArray;
import org.apache.mahout.cf.taste.impl.neighborhood.NearestNUserNeighborhood;
import org.apache.mahout.cf.taste.impl.recommender.GenericUserBasedRecommender;
import org.apache.mahout.cf.taste.impl.similarity.PearsonCorrelationSimilarity;
import org.apache.mahout.cf.taste.model.DataModel;
import org.apache.mahout.cf.taste.model.PreferenceArray;
import org.apache.mahout.cf.taste.neighborhood.UserNeighborhood;
import org.apache.mahout.cf.taste.recommender.RecommendedItem;
import org.apache.mahout.cf.taste.similarity.UserSimilarity;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import pers.qianyu.module.core.comm.Pagination;
import pers.qianyu.module.core.domain.image.entity.ImageLabelPO;
import pers.qianyu.module.core.domain.image.entity.ImagePO;
import pers.qianyu.module.core.domain.image.query.ImageQuery;
import pers.qianyu.module.core.domain.image.vo.ImageVO;
import pers.qianyu.module.core.domain.recommend.dataobj.UserLabelPreferenceDO;
import pers.qianyu.module.core.domain.recommend.model.RecoLabelItem;
import pers.qianyu.module.core.domain.system.vo.SysUserVO;
import pers.qianyu.module.image.dao.ImageLabelDao;
import pers.qianyu.module.image.service.ImageService;
import pers.qianyu.module.recommend.dao.RecommendDao;
import pers.qianyu.module.recommend.service.RecommendService;
import pers.qianyu.module.security.holder.CurrentLoginUserHolder;

import java.util.*;
import java.util.stream.Collectors;

/**
 * @author mizzle rain
 * @date 2021-04-21 20:11
 */
@Service("recommendService")
public class RecommendServiceImpl implements RecommendService {
    private static final String RECOMMEND_INFO_KEY = "recommend:userId:labelIds";
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;
    @Autowired
    private RecommendDao recommendDao;
    @Autowired
    private ImageService imageService;
    @Autowired
    private ImageLabelDao imageLabelDao;

    private DataModel getDataModel() {
        // 从数据库中查询偏好数据
        List<UserLabelPreferenceDO> userLabelPreferenceList = recommendDao.findUserLabelPreferenceList();
        // 根据UserID分组
        Map<Long, List<UserLabelPreferenceDO>> longListMap = userLabelPreferenceList.stream()
                .collect(Collectors.groupingBy(UserLabelPreferenceDO::getUserId));
        // 构造DataModel
        FastByIDMap<PreferenceArray> preferences = new FastByIDMap<>();
        for (Map.Entry<Long, List<UserLabelPreferenceDO>> entry : longListMap.entrySet()) {
            Long key = entry.getKey();
            List<UserLabelPreferenceDO> value = entry.getValue();
            PreferenceArray userPref = new GenericUserPreferenceArray(value.size());
            userPref.setUserID(0, key);
            for (int i = 0; i < value.size(); i++) {
                UserLabelPreferenceDO userLabelPreferenceDO = value.get(i);
                userPref.setItemID(i, userLabelPreferenceDO.getLabelId());
                userPref.setValue(i, userLabelPreferenceDO.getCount());
            }
            preferences.put(key, userPref);
        }
        return new GenericDataModel(preferences);
    }

    @Override
    public void computeRecommend() {
        try {
            long start = System.currentTimeMillis();
            // 获取DataModel
            DataModel dataModel = getDataModel();
            // 使用皮尔逊相关性计算相似度
            UserSimilarity similarity = new PearsonCorrelationSimilarity(dataModel);
            // 计算近邻邻居，基于固定数量
            UserNeighborhood userNeighborhood = new NearestNUserNeighborhood(100, similarity, dataModel);
            // 构建推荐对象，基于UserItem
            GenericUserBasedRecommender recommender = new GenericUserBasedRecommender(dataModel, userNeighborhood, similarity);

            HashMap<Long, List<RecoLabelItem>> recommendMap = new HashMap<>();
            LongPrimitiveIterator userIdsIter = dataModel.getUserIDs();
            while (userIdsIter.hasNext()) {
                Long userId = userIdsIter.next();
                List<RecommendedItem> recommendedItemList = recommender.recommend(userId, 100, true);
                recommendMap.put(userId, recommendedItemList.stream()
                        .map(i -> new RecoLabelItem(i.getItemID(), i.getValue()))
                        .collect(Collectors.toList()));
            }
            long end = System.currentTimeMillis();
//            System.out.println(JSON.toJSONString(recommendMap, true));
            LogFactory.get().info("推荐计算完成，花费时间：{}s", (end - start) / 1000);
            // 将数据存入 Redis
            for (Map.Entry<Long, List<RecoLabelItem>> entry : recommendMap.entrySet()) {
                Long key = entry.getKey();
                List<RecoLabelItem> value = entry.getValue();
                redisTemplate.opsForHash().put(RECOMMEND_INFO_KEY, String.valueOf(key), JSON.toJSONString(value));
            }
        } catch (TasteException e) {
            e.printStackTrace();
        }
    }

    @Override
    public List<Long> getLabelIdsByUserId(Long userId) {
        String str = (String) redisTemplate.opsForHash().get(RECOMMEND_INFO_KEY, String.valueOf(userId));
        List<RecoLabelItem> recommendedItems = JSONObject.parseArray(str, RecoLabelItem.class);
        if (recommendedItems == null || recommendedItems.size() == 0) {
            return new ArrayList<>();
        }
        return recommendedItems.stream()
                .sorted(Comparator.comparing(RecoLabelItem::getValue).reversed())
                .map(RecoLabelItem::getItemId)
                .collect(Collectors.toList());
    }

    @Override
    public Pagination<ImageVO> getRecommendImages(ImageQuery imageQuery) {
        SysUserVO sysUserVO = CurrentLoginUserHolder.getLoginUser().getSysUserVO();
        List<Long> labelIds = getLabelIdsByUserId(sysUserVO.getId());
        if (labelIds.size() == 0) {
            return imageService.queryImage(imageQuery);
        }
        QueryWrapper<ImageLabelPO> qw1 = new QueryWrapper<>();
        qw1.in("label_id", labelIds);
        Set<Long> imageIds = imageLabelDao.selectList(qw1).stream()
                .map(ImageLabelPO::getImageId)
                .collect(Collectors.toSet());
        Page<ImagePO> page = new Page<>(imageQuery.getPage(), imageQuery.getLimit());
        QueryWrapper<ImagePO> wrapper = new QueryWrapper<>();
        wrapper.in("id", imageIds);
        Pagination<ImageVO> pagination = imageService.queryImagePageInfo(page, wrapper);
        if (CollUtil.isEmpty(pagination.getRecords())) {
            wrapper = new QueryWrapper<>();
            wrapper.notIn("id", labelIds);
            wrapper.orderByDesc("create_time");
            return imageService.queryImagePageInfo(page, wrapper);
        }
        return pagination;
    }
}
